#pragma once

#include "Log.hpp"
#include "InetAddr.hpp"

const int gdefaultsockfd =  -1;
const int gbacklog = 8;

// 基类：规定创建套接字的方法
// 子类：提供一个/若干个 固定模式的socket的方法
// 这种策略模式叫：模板方法模式
namespace SocketModule
{
    using namespace LogModule;
    class Socket;  // 声明
    using SockPtr = std::shared_ptr<Socket>;

    class Socket
    {
    public:
        virtual ~Socket() = default;
        virtual void SocketOrDie() = 0;                      // ::socket 创建套接字
        virtual void SetSockOpt() = 0;                    // 传给套接字的一些选项
        virtual bool BindOrDie(int port) = 0;               // ::bind 绑定套接字
        virtual bool ListenOrDie() = 0;                     // ::listen 设置监听状态
        virtual int Accepter(InetAddr* client) = 0;     // ::accept 获取新连接，返回值就是服务的sockfd
        virtual void Close() = 0;                           // 关闭文件描述符
        virtual int Recv(std::string* out) = 0;             // 读
        virtual int Send(std::string& in) = 0;              // 写
        virtual int Fd() = 0;

        // 提供一个创建listensockfd的固定方法
        // 调用之后就会用这里的方法，并使用子类的方法实现
        void BuildTcpSocketMethod(int port)
        {
            SocketOrDie();
            SetSockOpt();
            BindOrDie(port);
            ListenOrDie();
        }
    };

    class TcpSocket : public Socket
    {
    public:
        TcpSocket(int sockfd = gdefaultsockfd)
            :_sockfd(sockfd)
        {}

        ~TcpSocket() {}

        // ::socket 创建套接字
        virtual void SocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if(_sockfd < 0)
            {
                LOG(LogLevel::ERROR) << "socket failed";
                Die(SOCKET_ERR);
            }
            LOG(LogLevel::INFO) << "socket create successed: " << _sockfd;
        }   

        // 传给套接字的一些选项
        virtual void SetSockOpt() override
        {
            // 保证我们的服务器，异常断开之后，可以立即重启，不会有bind问题
            int opt = 1;
            int n = ::setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
            (void)n;
        }

        // ::bind 绑定套接字
        virtual bool BindOrDie(int port) override
        {
            if(_sockfd == gdefaultsockfd) return false;
            // struct sockaddr_in local;
            // bzero(&local, sizeof(local));
            // local.sin_addr.s_addr = INADDR_ANY;
            // local.sin_port = htons(_port);
            // local.sin_family = AF_INET;
            InetAddr local(port);
            int n = ::bind(_sockfd, local.NetAddr(), local.NetAddrLen());
            if(n < 0)
            {
                LOG(LogLevel::ERROR) << "bind failed";
                Die(BIND_ERR);
            }
            LOG(LogLevel::INFO) << "bind successed: " << _sockfd;
            return true;
        }

        // ::listen 设置监听状态
        virtual bool ListenOrDie() override
        {
            if(_sockfd == gdefaultsockfd) return false;
            int n = ::listen(_sockfd, gbacklog);
            if(n < 0)
            {
                LOG(LogLevel::ERROR) << "listen failed";
                Die(LISTEN_ERR);
            }
            LOG(LogLevel::INFO) << "listen successed: " << _sockfd;
            return true;
        }

        // ::accept 获取新连接，返回值就是服务的sockfd
        // client 作为一个输出型参数，我想要让远端客户的信息都存到这个client上面，这也能使得accept能获取到客户端的信息
        virtual int Accepter(InetAddr* client) override 
        {
            if(!client) return -1;
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = ::accept(_sockfd, CONV(&peer), &len);
            if(sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept failed";
                return -1;
            }
            client->SetAddr(peer, len);  // 将client设置一下，这样全部信息就都设置好了
            return sockfd;
            // return std::make_shared<TcpSocket>(sockfd);
        }

        // 关闭文件描述符
        virtual void Close() override
        {
            if(_sockfd == gdefaultsockfd) return;
            ::close(_sockfd);
        }

        virtual int Recv(std::string* out) override
        {
            char inbuffer[1024*8];
            int size = ::recv(_sockfd, inbuffer, sizeof(inbuffer)-1, 0);
            if(size > 0)
            {
                inbuffer[size] = 0;
                *out = inbuffer;
            }
            // 失败会返回 -1，成功返回读到的字节数
            return size;
        }   

        virtual int Send(std::string& in) override
        {
            int size = ::send(_sockfd, in.c_str(), in.size(), 0);
            return size;
        }     
        
        virtual int Fd() override
        {
            return _sockfd;
        }

    private:
        int _sockfd;  // 监听套接字
    };
}